/**
 * 
 */
package com.googlecode.cswish.struts.interceptor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.ServletContext;

import ognl.OgnlRuntime;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.googlecode.cswish.annotation.ParamScope;
import com.googlecode.cswish.annotation.ScopeValue;
import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.struts.InstantiatingNullHandler;
import com.googlecode.cswish.struts.Invoker;
import com.googlecode.cswish.struts.ListPropertyAccessor;
import com.googlecode.cswish.struts.Routing;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.FrameConstant;
import com.googlecode.cswish.util.InvocationUtil;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
/**
 * Enhance the default implement<br>
 * 
 * 1. Initialize the parameter according to ParamScope define
 * <p>
 * There's 2 places to define the scope:
 * <li> Use ParamScope in the Action class or business method
 * <li> Call "ps.scope" in the FreeMarker page
 * 
 * @author Jerry.Feng Date: 2008-4-2
 *
 */
public class ScopeParametersInterceptor extends AbstractInterceptor {
	
	private static final Log LOG = LogFactory.getLog(ScopeParametersInterceptor.class);
	
	@Resource
	private BeanIntrospector beanIntrospector;
	
	@Resource
	private InvocationUtil invocationUtil;

	@Resource
	protected ObjectFactory objectFactory;
	
	@Resource
	private Routing routing;
	
	@Inject
	private ServletContext servletContext;

	@Inject
	public void setContainer(Container container) {
		InstantiatingNullHandler nullHandler = new InstantiatingNullHandler();
		container.inject(nullHandler);
		OgnlRuntime.setNullHandler(Object.class, nullHandler);
		OgnlRuntime.setNullHandler(Object[].class, nullHandler);
		
		ListPropertyAccessor listPropertyAccessor = new ListPropertyAccessor();
		container.inject(listPropertyAccessor);
		OgnlRuntime.setPropertyAccessor(List.class, listPropertyAccessor);
		OgnlRuntime.setPropertyAccessor(ArrayList.class, listPropertyAccessor);
	}
	
	public String intercept(ActionInvocation invocation) throws Exception {
		// create the parameter object according to annotation
		updateAnnotationScope(invocation);
		
		return invocation.invoke();
	}
	
	private void updateAnnotationScope(ActionInvocation invocation){
		ActionContext ac = invocation.getInvocationContext();
		Map<?, ?> contextMap = ac.getContextMap();
        try {
        	ReflectionContextState.setCreatingNullObjects(contextMap, true);
        	ReflectionContextState.setDenyMethodExecution(contextMap, true);
        	ReflectionContextState.setReportingConversionErrors(contextMap, true);

        	// Provide OGNL support
        	ValueStack stack = ac.getActionInvocation().getStack();
        	stack.getContext().put(FrameConstant.STACK_FUNCTION, 
        			servletContext.getAttribute(FrameConstant.STACK_FUNCTION));
        	
        	updateAnnotationScope0(invocation);
        } finally {
        	ReflectionContextState.setCreatingNullObjects(contextMap, false);
        	ReflectionContextState.setDenyMethodExecution(contextMap, false);
        	ReflectionContextState.setReportingConversionErrors(contextMap, false);
        }
	}
	
	/**
	 * Use a Map to save the parameter information
	 * 
	 * @param invocation
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private void updateAnnotationScope0(ActionInvocation invocation){
		ActionConfig config = invocation.getProxy().getConfig();
		Invoker invoker = routing.action2Invoker(config.getClassName());
		String methodName = invoker.methodName;
		String className = beanIntrospector.getSimpleName(config.getClassName());
		
		Class<?> clazz = invocation.getAction().getClass();
		ModelInfo modelInfo = beanIntrospector.getModelInfo(clazz.getName());
		for (PropertyInfo propertyInfo : modelInfo.getInterfaceProperties()) {
			Method method = propertyInfo.getWriteMethod();
			// 1. parameter name & type
			String name = propertyInfo.getName();
			String defaultKey = invocationUtil.getDefaultKey(className, methodName, name);
			
			ParamScope paramScope = invocationUtil.getActionParamScope(defaultKey, method);
			if (paramScope == null) {
				continue;
			}
    		
    		// 2. provide the parameter default value
			ActionContext context = ActionContext.getContext();
			String scopeKey = invocationUtil.getScopeKey(context, defaultKey, className, methodName, name, paramScope);
    		Object oldValue = invocationUtil.findValueFromParamScope(context, scopeKey, className, methodName, name, paramScope);
				
			// update current parameter value to relevant scope
			switch (paramScope.scope()) {
			case SESSION:
				// FIXME: necessary to add the rule?
				if (oldValue == null) {
					oldValue = initParameter(propertyInfo);
				}
				context.getSession().put(scopeKey, oldValue);
				break;
			case REQUEST:
				context.getValueStack().setValue(name, oldValue);
				break;
			case APPLICATION:
				// FIXME: necessary to add the rule?
				if (oldValue == null) {
					oldValue = initParameter(propertyInfo);
				}
				context.getApplication().put(scopeKey, oldValue);
				break;
			case ALL:
				break;
			}

    		if (oldValue != null && paramScope.scope() != ScopeValue.REQUEST) {
    			// Find the action object and update the value
    			context.getValueStack().setValue(name, oldValue);
    		}
    	} // for
	}
	
	public Object initParameter(PropertyInfo propertyInfo) {
		String typeName = propertyInfo.getWriteMethod().getParameterTypes()[0].getName();
		try {
			return objectFactory.buildBean(typeName, null);
		} catch (Exception ex) {
			LOG.error("Fail to create bean: " + typeName, ex);
			return null;
		}
	}
	
	/**
	 * @param clazz
	 * @return
	 * @see Class.isPrimitive()
	 */
	private Object getPrimitiveObject(Class<?> clazz) {
		if (clazz == int.class) {
			return new Integer(0);
		} else if (clazz == long.class) {
			return new Long(0);
		} else if (clazz == boolean.class) {
			return Boolean.FALSE;
		} else if (clazz == double.class) {
			return new Double(0);
		} else if (clazz == char.class) {
			return new Character('\0');
		} else if (clazz == float.class) {
			return new Float(0);
		} else if (clazz == short.class) {
			return new Short((short)0);
		} else if (clazz == byte.class) {
			return new Byte((byte)0);
		} else { //if (clazz == void.class)
			return null;
		}
	}
	
	/**
	 * the method is copied from the alias class
	 * 
	 * @param parameters
	 * @return
	 */
	private String getParameterLogMap(Map parameters) {
        if (parameters == null) {
            return "NONE";
        }

        StringBuffer logEntry = new StringBuffer();
        for (Iterator paramIter = parameters.entrySet().iterator(); paramIter.hasNext();) {
            Map.Entry entry = (Map.Entry) paramIter.next();
            logEntry.append(String.valueOf(entry.getKey()));
            logEntry.append(" => ");
            if (entry.getValue() instanceof Object[]) {
                Object[] valueArray = (Object[]) entry.getValue();
                logEntry.append("[ ");
                for (int indexA = 0; indexA < (valueArray.length - 1); indexA++) {
                    Object valueAtIndex = valueArray[indexA];
                    logEntry.append(valueAtIndex);
                    logEntry.append(String.valueOf(valueAtIndex));
                    logEntry.append(", ");
                }
                logEntry.append(String.valueOf(valueArray[valueArray.length - 1]));
                logEntry.append(" ] ");
            } else {
                logEntry.append(String.valueOf(entry.getValue()));
            }
        }

        return logEntry.toString();
    }
}
